sure, to hebrew first: import {GoogleMap} from '../cmps/GoogleMap'
export function AboutUs() {
return (
<section>
<h2>About Us</h2>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Magni aperiam quo veniam velit dolor reprehenderit, laudantium consequatur neque numquam labore quae. Accusamus libero perferendis ducimus? Alias unde hic quisquam doloremque.</p>
<h3>Our Branches</h3>
<GoogleMap />
</section>
)
}
-------------------------------------------------------------------------
import { PieChart } from '../cmps/PieChart.jsx'
import { BarChart } from '../cmps/BarChart.jsx'
import { LineChart } from '../cmps/LineChart.jsx'
import { toyService } from '../services/toy.service.js'
import { loadToys } from '../store/actions/toy.actions.js'
import { useSelector } from 'react-redux'
import { useEffect } from 'react'
export function Dashboard() {
const toys = useSelector(storeState => storeState.toyModule.toys)
useEffect(() => {
loadToys()
}, [])
const toyLabels = toyService.getLabels(toys)
return (
<section className='toy-stats'>
<h1>Toys Store Statistics</h1>
<BarChart toys={toys} labels={toyLabels} />
<PieChart toys={toys} labels={toyLabels} />
<LineChart toys={toys} />
</section>
)
}
-----------------------------------------------------------------
import { useDispatch, useSelector } from "react-redux"
import { CHANGE_BY } from "../store/reducers/user.reducer.js"
import { useState } from "react"
export function HomePage() {
const dispatch = useDispatch()
const [_count, setCount] = useState(10)
const count = useSelector(storeState => storeState.userModule.count)
function changeCount(diff) {
setCount(count => count + diff)
dispatch({ type: INCREMENT })
dispatch({ type: CHANGE_BY, diff })
}
return (
<section>
<h2>
Count {count}
<button onClick={() => {
changeCount(1)
}}>+</button>
<button onClick={() => {
changeCount(10)
}}>+10</button>
</h2 >
<img src="./logo.png" />
</section >
)
}
---------------------------------------------------------------------
import { useEffect, useState } from "react"
import { toyService } from "../services/toy.service.js"
import { utilService } from "../services/util.service.js"
import { Link, useParams, useNavigate } from "react-router-dom"
import { Popup } from '../cmps/Popup'
import { Chat } from '../cmps/Chat'
// const { useEffect, useState } = React
export function ToyDetails() {
const [toy, setToy] = useState(null)
const { toyId } = useParams()
const [isOpen, setIsOpen] = useState(false)
const navigate = useNavigate()
useEffect(() => {
window.addEventListener('keyup', handleIsOpen)
return () => {
window.removeEventListener('keyup', handleIsOpen)
}
}, [])
useEffect(() => {
if (toyId) loadToy()
}, [toyId])
function handleIsOpen({ key }) {
if (key === 'Escape') setIsOpen(false)
}
function loadToy() {
toyService.getById(toyId)
.then(toy => setToy(toy))
.catch(err => {
console.log('Had issues in toy details', err)
navigate('/toy')
})
}
if (!toy) return <div>Loading...</div>
return (
<section className="toy-details">
<h1>Toy : {toy.name}</h1>
<h5>Price: ${toy.price}</h5>
<p><i className="fa-solid fa-puzzle-piece"></i></p>
{!!toy.labels?.length && (
<p>Labels: <span>{toy.labels.join(', ')}</span></p>
)}
<p className={toy.inStock ? 'green' : 'red'}>
{toy.inStock ? 'In stock' : 'Not in stock'}
</p>
<p>{utilService.makeLorem(30)}</p>
<div>
<button><Link to={/toy/edit/${toy._id}}>Edit</Link></button>
<button><Link to={/toy}>Back</Link></button>
</div>
<button className="btn" onClick={() => { setIsOpen(true) }} >
Chat
</button>
{/* <p>
<Link to="/toy/nJ5L4">Next Toy</Link>
</p> */}
{isOpen && (
<Popup
isOpen={isOpen}
onClose={() => setIsOpen(false)}
heading="Lets chat!"
footing={<button className="btn" onClick={() => setIsOpen(false)}>Close</button>}
>
<Chat />
</Popup>
)}
</section>
)
}
--------------------------------------------------------------------------------
import { useEffect, useState } from "react"
import { toyService } from "../services/toy.service.js"
import { showErrorMsg, showSuccessMsg } from "../services/event-bus.service.js"
import { saveToy } from "../store/actions/toy.actions.js"
import { Link, useNavigate, useParams } from "react-router-dom"
import { useOnlineStatus } from "../hooks/useOnlineStatus.js"
import { useConfirmTabClose } from "../hooks/useConfirmTabClose.js"
export function ToyEdit() {
const navigate = useNavigate()
const [toyToEdit, setToyToEdit] = useState(toyService.getEmptyToy())
const { toyId } = useParams()
const isOnline = useOnlineStatus()
const setHasUnsavedChanges = useConfirmTabClose()
useEffect(() => {
if (toyId) loadToy()
}, [])
function loadToy() {
toyService.getById(toyId)
.then(toy => setToyToEdit(toy))
.catch(err => {
console.log('Had issues in toy edit', err)
navigate('/toy')
})
}
function handleChange({ target }) {
let { value, type, name: field } = target
value = type === 'number' ? +value : value
setToyToEdit((prevToy) => ({ ...prevToy, [field]: value }))
setHasUnsavedChanges(true)
}
function onSaveToy(ev) {
ev.preventDefault()
if (!toyToEdit.price) toyToEdit.price = 100
saveToy(toyToEdit)
.then(() => {
showSuccessMsg('Toy Saved!')
navigate('/toy')
})
.catch(err => {
console.log('Had issues in toy details', err)
showErrorMsg('Had issues in toy details')
})
}
return (
<section className="toy-edit">
<h2>{toyToEdit._id ? 'Edit' : 'Add'} Toy</h2>
<form onSubmit={onSaveToy} >
<label htmlFor="name">Toy Name : </label>
<input type="text"
name="name"
id="name"
placeholder="Enter toy name..."
value={toyToEdit.name}
onChange={handleChange}
/>
<label htmlFor="price">Price : </label>
<input type="number"
name="price"
id="price"
placeholder="Enter price"
value={toyToEdit.price}
onChange={handleChange}
/>
<div>
<button>{toyToEdit._id ? 'Save' : 'Add'}</button>
<Link to="/toy">Cancel</Link>
</div>
<section>
<h1>{isOnline ? '✅ Online' : '❌ Disconnected'}</h1>
</section>
</form>
</section>
)
}
------------------------------------------------------------
import { useDispatch, useSelector } from 'react-redux'
import { ToyFilter } from '../cmps/ToyFilter.jsx'
import { ToyList } from '../cmps/ToyList.jsx'
import { toyService } from '../services/toy.service.js'
import { showSuccessMsg, showErrorMsg } from '../services/event-bus.service.js'
import { loadToys, removeToyOptimistic, saveToy, setFilterBy } from '../store/actions/toy.actions.js'
import { ADD_TOY_TO_CART } from '../store/reducers/toy.reducer.js'
import { useEffect } from 'react'
import { Link } from 'react-router-dom'
export function ToyIndex() {
const dispatch = useDispatch()
const toys = useSelector(storeState => storeState.toyModule.toys)
const filterBy = useSelector(storeState => storeState.toyModule.filterBy)
const isLoading = useSelector(storeState => storeState.toyModule.isLoading)
useEffect(() => {
loadToys()
.catch(() => {
showErrorMsg('Cannot load toys!')
})
// console.log(filterBy);
}, [filterBy])
function onSetFilter(filterBy) {
setFilterBy(filterBy)
}
function onRemoveToy(toyId) {
removeToyOptimistic(toyId)
.then(() => {
showSuccessMsg('toy removed')
})
.catch(err => {
showErrorMsg('Cannot remove toy')
})
}
function onAddToy() {
const toyToSave = toyService.getRandomToy()
saveToy(toyToSave)
.then((savedToy) => {
showSuccessMsg(toy added (id: ${savedToy._id}))
})
.catch(err => {
showErrorMsg('Cannot add toy')
})
}
function onEditToy(toy) {
const price = +prompt('New price?')
const toyToSave = { ...toy, price }
saveToy(toyToSave)
.then((savedToy) => {
showSuccessMsg(toy updated to price: $${savedToy.price})
})
.catch(err => {
showErrorMsg('Cannot update toy')
})
}
function addToCart(toy) {
console.log(Adding ${toy.name} to cart)
dispatch({ type: ADD_TOY_TO_CART, toy })
showSuccessMsg('Added to cart')
}
return (
<div>
<h3>Toy Store App</h3>
<main>
<Link to="/toy/edit">Add Toy</Link>
<button className='add-btn'
onClick={onAddToy}>Add Random Toy
<i className="fa-solid fa-puzzle-piece"></i>
</button>
<ToyFilter filterBy={filterBy} onSetFilter={onSetFilter} />
{!isLoading
? <ToyList
toys={toys}
onRemoveToy={onRemoveToy}
onEditToy={onEditToy}
addToCart={addToCart}
/>
: <div>Loading...</div>
}
<hr />
</main>
</div>
)
}
-------------------------------------------------------------------------------------
import { useEffect, useState } from "react"
import { userService } from "../services/user.service.js"
import { Link, useNavigate, useParams } from "react-router-dom"
// const { useEffect, useState } = React
// const { Link, useParams, useNavigate } = ReactRouterDOM
export function UserDetails() {
const [user, setUser] = useState(null)
const { userId } = useParams()
const navigate = useNavigate()
useEffect(() => {
if (userId) loadUser()
}, [userId])
function loadUser() {
userService.getById(userId)
.then(user => {
console.log('user:', user)
setUser(user)
})
.catch(err => {
console.log('Had issues in user details', err)
navigate('/')
})
}
if (!user) return <div>Loading...</div>
const loggedInUser = userService.getLoggedinUser()
const isMyProfile = loggedInUser._id === userId
return (
<section className="user-details">
<h1>Fullname: {user.fullname}</h1>
<h5>Credits: {user.credits}</h5>
{isMyProfile && (
<section>
<h2>My Stuff!</h2>
</section>
)}
<p>@</p>
<p>User is so lorem ipsum dolor sit amet, consectetur adipisicing elit. Animi voluptas cumque tempore, aperiam sed dolorum rem! Nemo quidem, placeat perferendis tempora aspernatur sit, explicabo veritatis corrupti perspiciatis repellat, enim quibusdam!</p>
<Link to="/">Home</Link>
</section>
)
}
--------------------------------------------------------------------------------------------------
import { UserMsg } from './UserMsg.jsx'
import { ShoppingCart } from './ShoppingCart.jsx'
import { TOGGLE_CART_IS_SHOWN } from '../store/reducers/toy.reducer.js'
import { useDispatch, useSelector } from 'react-redux'
export function AppFooter() {
const dispatch = useDispatch()
const isCartShown = useSelector(storeState => storeState.toyModule.isCartShown)
const count = useSelector(storeState => storeState.userModule.count)
const toysLength = useSelector(storeState => storeState.toyModule.toys.length)
const shoppingCartLength = useSelector(storeState => storeState.toyModule.shoppingCart.length)
return (
<footer className='app-footer'>
<h5>
Currently {toysLength} toys in the shop
</h5>
<p>
Coffeerights to all - Count: {count}
</p>
<h5>
<span>{shoppingCartLength}</span> Products in your Cart
<a href="#" onClick={(ev) => {
ev.preventDefault()
dispatch({ type: TOGGLE_CART_IS_SHOWN })
}}>
({(isCartShown) ? 'hide' : 'show'})
</a>
</h5>
<ShoppingCart isCartShown={isCartShown} />
<UserMsg />
</footer>
)
}
-----------------------------------------------------------------------------------------------------------
import { UserMsg } from './UserMsg.jsx'
import { LoginSignup } from './LoginSignup.jsx'
import { showErrorMsg, showSuccessMsg } from '../services/event-bus.service.js'
import { logout } from '../store/actions/user.actions.js'
import { TOGGLE_CART_IS_SHOWN } from '../store/reducers/toy.reducer.js'
import { useDispatch, useSelector } from 'react-redux'
import { NavLink } from 'react-router-dom'
import { useTranslation } from "react-i18next"
export function AppHeader() {
const dispatch = useDispatch()
const user = useSelector(storeState => storeState.userModule.loggedInUser)
const { t, i18n } = useTranslation()
function onLogout() {
logout()
.then(() => {
showSuccessMsg('logout successfully')
})
.catch((err) => {
showErrorMsg('OOPs try again')
})
}
function onToggleCart(ev) {
ev.preventDefault()
dispatch({ type: TOGGLE_CART_IS_SHOWN })
}
// console.log(user.credits);
return (
<header className="app-header full main-layout">
<section className="header-container">
<h1>Toys Store App</h1>
<button onClick={() => i18n.changeLanguage("en")}>English</button>
<button onClick={() => i18n.changeLanguage("he")}>עברית</button>
<nav className="app-nav">
<NavLink to="/" >Home</NavLink>
<NavLink to="/about" >About</NavLink>
<NavLink to="/toy" >Toys</NavLink>
<NavLink to="/dashboard" >Dashboard</NavLink>
<a onClick={onToggleCart} href="#"><i className="fa-solid fa-cart-shopping"></i> Cart</a>
</nav>
</section>
{user ? (
< section >
<span to={/user/${user._id}}>Hello {user.fullname} <span>${user.credits.toLocaleString()}</span></span>
<button onClick={onLogout}>Logout</button>
</ section >
) : (
<section>
<LoginSignup />
</section>
)}
<UserMsg />
</header>
)
}
------------------------------------------------------------------------------------------------------
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend,
} from "chart.js";
import { Bar } from "react-chartjs-2"
ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend)
export function BarChart({ labels, toys }) {
const options = {
responsive: true,
plugins: {
legend: {
position: "top",
}
},
}
const data = {
labels: labels,
datasets: [
{
label: 'Average',
data: labels.map(lbl => {
const filtered = toys.filter(toy => toy.inStock && toy.labels.includes(lbl))
return filtered.length
? Math.floor(filtered.reduce((acc, toy) => (acc + toy.price), 0) / filtered.length)
: 0
}),
backgroundColor: "rgba(255, 99, 132, 0.5)",
},
],
}
return (
<section className="bar-chart chart">
<h2>Average prices per label</h2>
<Bar options={options} data={data} />
</section>
)
}
------------------------------------------------------------------------------------------------
import { useState } from 'react'
export function Chat() {
const [msgs, setMsgs] = useState([])
const [msgToSend, setMsgToSend] = useState('')
function sendMsg(ev) {
ev.preventDefault()
if (!msgToSend.trim()) return
const userMsg = { text: msgToSend, sender: 'user' }
setMsgs(prevMsgs => [...prevMsgs, userMsg])
setMsgToSend('')
setTimeout(() => {
const botMsg = { text: 'Bot: Sure, no problems', sender: 'bot' }
setMsgs(prevMsgs => [...prevMsgs, botMsg])
}, 1000)
}
return (
<section className="chat-container">
<div className="chat-msgs">
{msgs.map((msg, idx) => (
<div key={idx} className={chat-msg ${msg.sender}}>
{msg.text}
</div>
))}
</div>
<div className="chat-input">
<form onSubmit={sendMsg}>
<input
type="text"
value={msgToSend}
onChange={ev => setMsgToSend(ev.target.value)}
placeholder="Type a message..."
autoComplete="off"
/>
<button className="btn">Send</button>
</form>
</div>
</section>
)
}
----------------------------------------------------------------------------------------------------------
const API_KEY = import.meta.env.VITE_MY_API_KEY
import { useRef, useState } from "react"
import {
AdvancedMarker,
APIProvider,
InfoWindow,
Map,
useAdvancedMarkerRef
} from "@vis.gl/react-google-maps"
function ClickableMarker({ position, onClick }) {
const [markerRef, marker] = useAdvancedMarkerRef()
return (
<AdvancedMarker
position={position}
ref={markerRef}
onClick={() => onClick(marker)}
/>
)
}
export function GoogleMap() {
const [coords, setCoords] = useState({ lat: 32.0853, lng: 34.7818 })
const [isOpen, setIsOpen] = useState(false)
const [activeMarker, setActiveMarker] = useState(null)
const mapRef = useRef(null)
function handleMapClick(ev) {
const pos = ev.detail?.latLng;
if (!pos) return
setCoords(pos)
setIsOpen(false)
}
function handleMarkerClick(marker) {
const pos = marker.position;
setCoords(pos)
setActiveMarker(marker)
setIsOpen(true)
}
const markers = [
{ lat: 32.0853, lng: 34.7818 }, // Tel Aviv
{ lat: 32.44391, lng: 34.91762 }, // Hadera
{ lat: 32.01672, lng: 34.74581 }, // Bat Yam
]
return (
<div className="map" style={{ height: '100vh', width: '100%' }}>
<APIProvider apiKey={API_KEY}>
<Map
defaultCenter={coords}
defaultZoom={10}
mapId="MAP_ID"
gestureHandling={'greedy'}
disableDefaultUI={false}
onClick={handleMapClick}
>
{markers.map(pos => (
<ClickableMarker
key={${pos.lat}-${pos.lng}}
position={pos}
onClick={handleMarkerClick}
/>
))}
{isOpen &&
<InfoWindow
anchor={activeMarker}
onCloseClick={() => setIsOpen(false)}
>
<h3>Marker is at {JSON.stringify(coords)}</h3>
</InfoWindow>
}
</Map>
</APIProvider>
</div>
)
}
-------------------------------------------------------------------------------------
import {SelectSmall} from './Select.jsx'
export function LabelSelector({ labels, onLabelChange }) {
const formattedLbls = Object.fromEntries(labels.map(lbl => [lbl, lbl]))
return (
<SelectSmall
inputLbl={'labels'}
options={formattedLbls}
onChange={onLabelChange}/>
)
}
------------------------------------------------------------------------------------------
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend,
} from "chart.js";
import { Line } from "react-chartjs-2";
ChartJS.register(
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend
)
export function LineChart({ toys }) {
const dates = toys
.map(toy => toy.createdAt)
.sort((a, b) => a - b)
.map(ts => new Date(ts).toLocaleDateString("en-US", {
month: "short", year: "numeric",
})
)
const options = {
responsive: true,
plugins: {
legend: {
position: "top",
},
},
}
const labels = [...new Set(dates)]
const data = {
labels,
datasets: [
{
label: "Date",
data: labels.map(lbl =>
dates.filter(dt => dt === lbl).length),
borderColor: "rgb(53, 162, 235)",
backgroundColor: "rgba(53, 162, 235, 0.5)",
},
],
}
return (
<section className="line-chart chart">
<h2>Toys added by date</h2>
<Line options={options} data={data} />
</section>
)
}
-----------------------------------------------------------------------------------------
import { useState } from "react"
import { userService } from "../services/user.service.js"
export function LoginForm({ onLogin, isSignup }) {
const [credentials, setCredentials] = useState(userService.getEmptyCredentials())
function handleChange({ target }) {
const { name: field, value } = target
setCredentials(prevCreds => ({ ...prevCreds, [field]: value }))
}
function handleSubmit(ev) {
ev.preventDefault()
onLogin(credentials)
}
return (
<form className="login-form" onSubmit={handleSubmit}>
<input
type="text"
name="username"
value={credentials.username}
placeholder="Username"
onChange={handleChange}
required
autoFocus
/>
<input
type="password"
name="password"
value={credentials.password}
placeholder="Password"
onChange={handleChange}
required
autoComplete="off"
/>
{isSignup && <input
type="text"
name="fullname"
value={credentials.fullname}
placeholder="Full name"
onChange={handleChange}
required
/>}
<button>{isSignup ? 'Signup' : 'Login'}</button>
</form>
)
}
-----------------------------------------------------------------------------------------------------------
import { useState } from 'react'
import { showErrorMsg, showSuccessMsg } from '../services/event-bus.service.js'
import { login, signup } from '../store/actions/user.actions.js'
import { LoginForm } from './LoginForm.jsx'
export function LoginSignup() {
const [isSignup, setIsSignUp] = useState(false)
function onLogin(credentials) {
isSignup ? _signup(credentials) : _login(credentials)
}
function _login(credentials) {
login(credentials)
.then(() => { showSuccessMsg('Logged in successfully') })
.catch((err) => { showErrorMsg('Oops try again') })
}
function _signup(credentials) {
signup(credentials)
.then(() => { showSuccessMsg('Signed in successfully') })
.catch((err) => { showErrorMsg('Oops try again') })
}
return (
<div className="login-page">
<LoginForm
onLogin={onLogin}
isSignup={isSignup}
/>
<div className="btns">
<a href="#" onClick={() => setIsSignUp(!isSignup)}>
{isSignup ?
'Already a member? Login' :
'New user? Signup here'
}
</a >
</div>
</div >
)
}
----------------------------------------------------------------------------------------------
import { Chart as ChartJS, ArcElement, Tooltip, Legend, RadialLinearScale } from 'chart.js';
import { Doughnut, PolarArea } from 'react-chartjs-2';
ChartJS.register(RadialLinearScale, ArcElement, Tooltip, Legend);
export function PieChart({ labels, toys }) {
const data = {
labels: labels,
datasets: [
{
label: 'Amount:',
data: labels.map(lbl =>
toys.reduce((acc, toy) =>
acc + (toy.labels.includes(lbl) && toy.inStock ? 1 : 0), 0)
),
backgroundColor: [
'rgba(255, 99, 132, 0.2)',
'rgba(54, 162, 235, 0.2)',
'rgba(255, 206, 86, 0.2)',
'rgba(75, 192, 192, 0.2)',
'rgba(153, 102, 255, 0.2)',
'rgba(255, 159, 64, 0.2)',
],
borderColor: [
'rgba(255, 99, 132, 1)',
'rgba(54, 162, 235, 1)',
'rgba(255, 206, 86, 1)',
'rgba(75, 192, 192, 1)',
'rgba(153, 102, 255, 1)',
'rgba(255, 159, 64, 1)',
],
borderWidth: 1,
},
],
}
return (
<section className="pie-chart chart">
<h2>Inventory by label</h2>
<Doughnut data={data} />
</section>
)
}
--------------------------------------------------------------------------------------
export function Popup({ heading, children, footing, onClose }) {
return (
<div className="popup-overlay" onClick={onClose}>
<div className="popup-container" onClick={ev => ev.stopPropagation()}>
{heading && <header className="popup-header">{heading}</header>}
<main className="popup-main">{children}</main>
{footing && <footer className="popup-footer">{footing}</footer>}
</div>
</div>
)
}
-----------------------------------------------------------------------------------------
import { useEffect, useState } from "react"
import InputLabel from '@mui/material/InputLabel'
import MenuItem from '@mui/material/MenuItem'
import FormControl from '@mui/material/FormControl'
import Select from '@mui/material/Select'
export function SelectSmall({ inputLbl, options, onChange }) {
const [selectedOpt, setSelectedOpt] = useState('')
useEffect(() => {
onChange(selectedOpt)
}, [selectedOpt])
function handleChange(ev) {
const newOpt = ev.target.value
setSelectedOpt(newOpt)
}
return (
<FormControl sx={{ m: 1, minWidth: 100, maxWidth: 100 }} size="small">
<InputLabel id={${inputLbl} select-small-label}>
{inputLbl.charAt(0).toUpperCase() + inputLbl.slice(1)}
</InputLabel>
<Select
className="select-small"
labelId={${inputLbl} select-small-label}
id={${inputLbl} select-small}
value={selectedOpt}
label={inputLbl}
onChange={(ev) => handleChange(ev)}
>
<MenuItem
value="">
<em></em>
</MenuItem>
{Object.entries(options).map(([key, value]) =>
<MenuItem
key={key}
value={key}>
<em>{value}</em>
</MenuItem>)
}
</Select>
</FormControl>
)
}
-----------------------------------------------------------------------------------------------
import { useDispatch, useSelector } from 'react-redux'
import { showSuccessMsg, showErrorMsg } from '../services/event-bus.service.js'
import { checkout } from '../store/actions/user.actions.js'
import { REMOVE_TOY_FROM_CART } from '../store/reducers/toy.reducer.js'
export function ShoppingCart({ isCartShown }) {
const dispatch = useDispatch()
const shoppingCart = useSelector(storeState => storeState.toyModule.shoppingCart)
const user = useSelector(storeState => storeState.userModule.loggedInUser)
function removeFromCart(toyId) {
console.log(Todo: remove: ${toyId} from cart)
dispatch({ type: REMOVE_TOY_FROM_CART, toyId })
}
function getCartTotal() {
return shoppingCart.reduce((acc, toy) => acc + toy.price, 0)
}
function onCheckout() {
const amount = getCartTotal()
// DONE: checkout function that dispatch
checkout(amount)
.then(()=>{
showSuccessMsg(Charged you: $ ${amount.toLocaleString()})
})
.catch(()=>{
showErrorMsg('There was a problem checking out!')
})
}
if (!isCartShown) return <span></span>
const total = getCartTotal()
return (
<section className="cart" >
<h5>Your Cart</h5>
<ul>
{
shoppingCart.map((toy, idx) => <li key={idx}>
<button onClick={() => {
removeFromCart(toy._id)
}}>x</button>
{toy.name} | ${toy.price}
</li>)
}
</ul>
<p>Total: ${total} </p>
<button disabled={!user || !total} onClick={onCheckout}>Checkout</button>
</section>
)
}
---------------------------------------------------------------------------------
import { useEffect, useRef, useState } from "react"
import { useSelector } from 'react-redux'
import { utilService } from "../services/util.service.js"
import { LabelSelector } from './LabelSelect.jsx'
import { ToySort } from './ToySort.jsx'
import { SelectSmall } from './Select.jsx'
export function ToyFilter({ filterBy, onSetFilter }) {
const [filterByToEdit, setFilterByToEdit] = useState({ ...filterBy })
const debouncedOnSetFilter = useRef(utilService.debounce(onSetFilter, 300))
const labels = useSelector(storeState => storeState.toyModule.labels)
useEffect(() => {
debouncedOnSetFilter.current(filterByToEdit)
}, [filterByToEdit])
function handleChange({ target }) {
let { value, name: field, type } = target
value = type === 'number' ? +value : value
setFilterByToEdit((prevFilter) => ({ ...prevFilter, [field]: value }))
}
function onLabelChange(selected) {
setFilterByToEdit((prevFilter) => ({ ...prevFilter, labels: selected }))
}
function onStockChange(selected) {
setFilterByToEdit((prevFilter) => ({ ...prevFilter, stock: selected }))
}
return (
<section className="toy-filter full main-layout">
<h2>Toys Filter</h2>
<form >
<label htmlFor="vendor">Name:</label>
<input type="text"
id="vendor"
name="txt"
placeholder="By name"
value={filterByToEdit.txt}
onChange={handleChange}
/>
<label htmlFor="maxPrice">Max price:</label>
<input type="number"
id="maxPrice"
name="maxPrice"
placeholder="By max price"
value={filterByToEdit.maxPrice || ''}
onChange={handleChange}
/>
</form>
<LabelSelector labels={labels} onLabelChange={onLabelChange} />
<ToySort onSetFilter={setFilterByToEdit} />
<SelectSmall
inputLbl={'stock'}
options={{ true: 'In Stock', false: 'Out of Stock' }}
onChange={onStockChange} />
</section>
)
}
------------------------------------------------------------------------------
import { Link } from "react-router-dom"
import { ToyPreview } from "./ToyPreview.jsx"
export function ToyList({ toys, onRemoveToy, onEditToy, addToCart }) {
return (
<ul className="toy-list">
{toys.map(toy =>
<li className="toy-preview" key={toy._id}>
<ToyPreview toy={toy} />
<div>
<button onClick={() => onRemoveToy(toy._id)}>x</button>
<button><Link to={/toy/edit/${toy._id}}>Edit</Link></button>
<button><Link to={/toy/${toy._id}}>Details</Link></button>
</div>
<button className="buy" onClick={() => addToCart(toy)}>
Add to cart
</button>
</li>)}
</ul>
)
}
-----------------------------------------------------------------------------------------
import { Link } from "react-router-dom";
export function ToyPreview({ toy }) {
return (
<article>
<h4>{toy.name}</h4>
<h1><i className="fa-solid fa-puzzle-piece"></i></h1>
<p>Price: <span>${toy.price.toLocaleString()}</span></p>
{toy.creator && <p>Creator: <Link to={/user/${toy.creator._id}}>{toy.creator.fullname}</Link></p>}
<hr />
</article>
)
}
-------------------------------------------------------------------------------------------
import { SelectSmall } from './Select.jsx'
export function ToySort({ onSetFilter }) {
function handleChange(value) {
onSetFilter(prev => ({ ...prev, sortBy: value }))
}
const options = { txt: 'Text', price: 'Price', created: 'Created At' }
return (
<SelectSmall
inputLbl={'sort'}
options={options}
onChange={handleChange} />
)
}
----------------------------------------------------------------------------------------------
import { useEffect, useRef, useState } from "react"
import { eventBusService } from "../services/event-bus.service.js"
// const { useState, useEffect, useRef } = React
export function UserMsg() {
const [msg, setMsg] = useState(null)
const timeoutIdRef = useRef()
useEffect(() => {
const unsubscribe = eventBusService.on('show-user-msg', (msg) => {
setMsg(msg)
// window.scrollTo({top: 0, behavior: 'smooth'});
if (timeoutIdRef.current) {
timeoutIdRef.current = null
clearTimeout(timeoutIdRef.current)
}
timeoutIdRef.current = setTimeout(closeMsg, 3000)
})
return unsubscribe
}, [])
function closeMsg() {
setMsg(null)
}
if (!msg) return <span></span>
return (
<section className={user-msg ${msg.type}}>
<button onClick={closeMsg}>x</button>
{msg.txt}
</section>
)
}